Composition


Inheritance is useful, but another way to do the exact same thing is just to use other classes and modules, rather than rely on implicit inheritance. If you look at the three ways to exploit inheritance, two of the three involve writing new code to replace or alter functionality. This can easily be replicated by just calling functions in a module. Here's an example of doing this:


In [ ]:
class Spa():
    def __init__(self):
        print("in init of spa")

class CoffeeShop():
    def __init__(self):
        print("in init of coffeeshop")
        
class Laundery():
    def __init__(self):
        print("in init of Laundery")

class Hotel():
    def __init__(self):
        print("Hotel init")
        self.spa = Spa()
        self.cs = CoffeeShop()
        self.shop = Shop()

In [1]:
class Other():
    def override(self):
        print("OTHER override()")

    def implicit(self):
        print("OTHER implicit()")

    def altered(self):
        print("OTHER altered()")
        
    def test(self):
        print("Test")


class Child():
    x = 12
    def __init__(self):
        Child.other = Other()

    def implicit(self):
        self.x = 11
        self.other.implicit()

    def override(self):
        print("CHILD override()")

    def altered(self):
        print("CHILD, BEFORE OTHER altered()")
        self.other.altered()
        print("CHILD, AFTER OTHER altered()")

son = Child()
girl = Child()

son.other.test()
son.implicit()
print(girl.x)
son.override()
son.altered()


Test
OTHER implicit()
12
CHILD override()
CHILD, BEFORE OTHER altered()
OTHER altered()
CHILD, AFTER OTHER altered()

In [7]:
class Other():
    a = 10
    def override(self):
        print("OTHER override()")

    def implicit(self):
        print("OTHER implicit()")

    def altered(self):
        print("OTHER altered()")
        
    def test(self):
        print("Test")


class Child():
    x = 12
    def __init__(self):
        Child.other = Other()

    def implicit(self):
        self.x = 11
        self.other.implicit()

    def override(self):
        print("CHILD override()")

    def altered(self):
        print("CHILD, BEFORE OTHER altered()")
        self.other.altered()
        print("CHILD, AFTER OTHER altered()")

son = Child()
girl = Child()
son.other.a = 1100

son.implicit()
print(girl.other.a)
print(id(son.other))
print(id(girl.other))


OTHER implicit()
1100
2488692178728
2488692178728

In [6]:
class Other():
    a = 10
    def override(self):
        print("OTHER override()")

    def implicit(self):
        print("OTHER implicit()")

    def altered(self):
        print("OTHER altered()")
        
    def test(self):
        print("Test")


class Child():
    x = 12
    def __init__(self):
        self.other = Other()

    def implicit(self):
        self.x = 11
        self.other.implicit()

    def override(self):
        print("CHILD override()")

    def altered(self):
        print("CHILD, BEFORE OTHER altered()")
        self.other.altered()
        print("CHILD, AFTER OTHER altered()")

son = Child()
girl = Child()
son.other.a = 1100

son.implicit()
print(girl.other.a)
print(id(son.other))
print(id(girl.other))


OTHER implicit()
10
2488692026392
2488692026672

When to Use Inheritance or Composition

The question of "inheritance versus composition" comes down to an attempt to solve the problem of reusable code. You don't want to have duplicated code all over your software, since that's not clean and efficient. Inheritance solves this problem by creating a mechanism for you to have implied features in base classes. Composition solves this by giving you modules and the ability to call functions in other classes.

If both solutions solve the problem of reuse, then which one is appropriate in which situations? The answer is incredibly subjective, but I'll give you my three guidelines for when to do which:

  • Avoid multiple inheritance at all costs, as it's too complex to be reliable. If you're stuck with it, then be prepared to know the class hierarchy and spend time finding where everything is coming from.
  • Use composition to package code into modules that are used in many different unrelated places and situations.
  • Use inheritance only when there are clearly related reusable pieces of code that fit under a single common concept or if you have to because of something you're using.

Do not be a slave to these rules. The thing to remember about object-oriented programming is that it is entirely a social convention programmers have created to package and share code. Because it's a social convention, but one that's codified in Python, you may be forced to avoid these rules because of the people you work with. In that case, find out how they use things and then just adapt to the situation.


In [ ]: